// $Id: socket.c 33 2011-04-14 18:49:15Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2010 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef WIN32
#include <winsock2.h>
#define INET_ADDRSTRLEN 16
#else
#include <arpa/inet.h>
#endif

#include "log.h"

#include "socket.h"

struct niosocket *niosocket_new(void)
{
	struct niosocket *sock;

	niodebug("constructing");

	if (!(sock = calloc(1, sizeof(*sock)))) {
		nioerror("failed to calloc");
		goto error;
	}

	sock->descriptor = INVALID_SOCKET;

	return sock;

error:
	niosocket_free(sock);
	return NULL;
}

void niosocket_free(struct niosocket *sock)
{
	niodebug("destructing: %p", sock);

	if (!sock) {
		return;
	}

	if (sock->descriptor != INVALID_SOCKET) {
		niodebug("closing socket: %d", sock->descriptor);

		close(sock->descriptor);
	}

	free(sock);
}

int niosocket_create(int type, SOCKET *descriptor)
{
	int rc;

	errno = 0;

	if ((*descriptor = socket(PF_INET, type, 0)) == INVALID_SOCKET) {
#ifdef WIN32
		rc = WSAGetLastError();
#else
		rc = errno ? errno : -1;
#endif
		nioerror("failed to make socket: %s", strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}

	errno = 0;

#ifdef WIN32
	unsigned long on = 1;
	if (ioctlsocket(descriptor, FIONBIO, (unsigned long*)&on)) {
		rc = WSAGetLastError();
#else
	if (fcntl(*descriptor, F_SETFL, O_NONBLOCK) == -1) {
		rc = errno ? errno : -1;
#endif
		nioerror("failed to make socket non-blocking: %s",
				strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}

	rc = 0;

finally:
	if (rc != 0) {
		if (*descriptor != INVALID_SOCKET) {
			close(*descriptor);
			*descriptor = INVALID_SOCKET;
		}
	}
	return rc;
}

int niosocket_connect(struct niosocket *sock,
		struct in_addr *address, unsigned short port)
{
	int rc;
	struct sockaddr_in saddr;
	char buffer[INET_ADDRSTRLEN];
	SOCKET descriptor = INVALID_SOCKET;

	if (!sock || !address) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (sock->descriptor != INVALID_SOCKET) {
		nioerror("already attached");
		rc = NIO_EOPENED;
		goto finally;
	}

	if (!inet_ntop(AF_INET, address, buffer, sizeof(buffer))) {
		nioerror("invalid address");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("opening channel to %s:%d", buffer, port);

	if ((rc = niosocket_create(SOCK_STREAM, &descriptor))) {
		nioerror("failed to make socket: %s", strerror(rc));
		goto finally;
	}

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	memcpy(&saddr.sin_addr, address, sizeof(saddr.sin_addr));

	errno = 0;

	if ((rc = connect(descriptor, (struct sockaddr*)&saddr,
			sizeof(struct sockaddr))) == -1) {
#ifdef WIN32
		rc = WSAGetLastError();
#else
		rc = errno ? errno : -1;
#endif
#ifdef WIN32
		if (rc != WSAEWOULDBLOCK && rc != WSAEINVAL &&
				rc != WSAEINPROGRESS) {
#else
		if (rc != EINPROGRESS) {
#endif
			nioerror("failed to connect: %s", strerror(rc));
			rc = NIO_EIO;
			goto finally;
		}
	}

	if ((rc = niosocket_attach(sock, descriptor))) {
		nioerror("failed to attach: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niosocket_set_address(sock, address, port))) {
		nioerror("failed to set address: %s", nio_strerror(rc));
		goto finally;
	}

finally:
	if (rc != NIO_EOK) {
		if (descriptor != INVALID_SOCKET) {
			close(descriptor);
		}
	}
	return rc;
}

int niosocket_listen(struct niosocket *sock,
		struct in_addr *address, unsigned short port,
		unsigned int backlog)
{
	int rc;
	struct sockaddr_in saddr;
	char buffer[INET_ADDRSTRLEN];
	SOCKET descriptor = INVALID_SOCKET;

	if (!sock || !address) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (sock->descriptor != INVALID_SOCKET) {
		nioerror("already attached");
		rc = NIO_EOPENED;
		goto finally;
	}

	if (!inet_ntop(AF_INET, address, buffer, sizeof(buffer))) {
		nioerror("invalid address");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("binding to %s:%d", buffer, port);

	if ((rc = niosocket_create(SOCK_STREAM, &descriptor))) {
		nioerror("failed to make socket: %s", nio_strerror(rc));
		goto finally;
	}

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	memcpy(&saddr.sin_addr, address, sizeof(saddr.sin_addr));

	errno = 0;

	if ((rc = bind(descriptor, (struct sockaddr*)&saddr,
			sizeof(struct sockaddr))) == -1) {
#ifdef WIN32
		rc = WSAGetLastError();
#else
		rc = errno ? errno : -1;
#endif
		nioerror("failed to bind: %s", strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}

	errno = 0;

	if((rc = listen(descriptor, backlog) == -1)) {
#ifdef WIN32
		rc = WSAGetLastError();
#else
		rc = errno ? errno : -1;
#endif
		nioerror("failed to listen: %s", strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}

	if ((rc = niosocket_attach(sock, descriptor))) {
		nioerror("failed to attach: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niosocket_set_address(sock, address, port))) {
		nioerror("failed to set address: %s", nio_strerror(rc));
		goto finally;
	}

finally:
	if (rc != NIO_EOK) {
		if (descriptor != INVALID_SOCKET) {
			close(descriptor);
		}
	}
	return rc;
}

int niosocket_accept(struct niosocket *sock, struct niosocket *accepted)
{
	int rc;
	struct sockaddr_in in;
	SOCKET descriptor = INVALID_SOCKET;
	socklen_t len;
	char ntop[16];

	if (!sock || !accepted) {
		nioerror("null argument(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (accepted->descriptor != INVALID_SOCKET) {
		nioerror("already opened or bound");
		rc = NIO_EOPENED;
		goto finally;
	}

	niodebug("accepting connection");

	len = sizeof(in);

	errno = 0;

	if ((descriptor = accept(sock->descriptor,
			(struct sockaddr *)&in, &len)) == INVALID_SOCKET) {
		rc = errno;
		nioerror("failed to accept socket");
		goto finally;
	}

	if (!(inet_ntop(AF_INET, (void*)&in.sin_addr, ntop,
			sizeof(ntop)))) {
		nioerror("failed to convert IP address");
	} else {
		niodebug("socket '%d' accepted from %s:%d",
				descriptor, ntop, in.sin_port);
	}

	if ((rc = niosocket_attach(accepted, descriptor))) {
		nioerror("failed to attach: %s", nio_strerror(rc));
		goto finally;
	}

	if ((rc = niosocket_set_address(accepted,
			&in.sin_addr, ntohs(in.sin_port)))) {
		nioerror("failed to set address: %s", nio_strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	if (rc != NIO_EOK) {
		if (descriptor != INVALID_SOCKET) {
			close(descriptor);
		}
	}
	return rc;
}

int niosocket_attach(struct niosocket *sock, SOCKET descriptor)
{
	int rc;

	if (!sock) {
		nioerror("null pointer(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (descriptor == INVALID_SOCKET) {
		nioerror("invalid descriptor");
		rc = NIO_EINVAL;
		goto finally;
	}

	if (sock->descriptor != INVALID_SOCKET) {
		nioerror("already attached");
		rc = NIO_EOPENED;
		goto finally;
	}

#ifdef WIN32
	unsigned long on = 1;
	if (ioctlsocket(sock->descriptor, FIONBIO, (unsigned long*)&on)) {
		rc = WSAGetLastError();
#else
	if (fcntl(descriptor, F_SETFL, O_NONBLOCK) == -1) {
		rc = errno ? errno : -1;
#endif
		nioerror("failed to make socket non-blocking: %s",
				strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}

	sock->descriptor = descriptor;
	rc = 0;

finally:
	return rc;
}

int niosocket_set_address(struct niosocket *sock,
		struct in_addr *address, unsigned short port)
{
	int rc;

	if (!sock || !address) {
		nioerror("null pointer(s)");
		rc = NIO_EINVAL;
		goto finally;
	}

	memcpy(&sock->address, address, sizeof(sock->address));
	sock->port = port;
	rc = 0;

finally:
	return rc;
}

int niosocket_close(struct niosocket *sock)
{
	int rc;
	char buffer[INET_ADDRSTRLEN];

	if (sock->descriptor == INVALID_SOCKET) {
		nioerror("not opened");
		rc = NIO_ECLOSED;
		goto finally;
	}

	if (!inet_ntop(AF_INET, &sock->address, buffer, sizeof(buffer))) {
		nioerror("invalid address");
		rc = NIO_EINVAL;
		goto finally;
	}

	niodebug("closing channel to %s:%d", buffer, sock->port);

#ifdef WIN32
	if ((rc = closesocket(sock->descriptor)) < 0) {
		rc = WSAGetLastError();
		nioerror("failed to close: %s", strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}
#else
	errno = 0;

	if ((rc = close(sock->descriptor)) < 0) {
		rc = errno ? errno : -1;
		nioerror("failed to close: %s", strerror(rc));
		rc = NIO_EIO;
		goto finally;
	}
#endif

	sock->descriptor = INVALID_SOCKET;
	rc = 0;

finally:
	return rc;
}

SOCKET niosocket_describe(struct niosocket *socket)
{
	return socket->descriptor;
}

ssize_t niosocket_read(struct niosocket *socket, void *buf, size_t count)
{
	niodebug("reading socket: %d", socket->descriptor);

#ifdef WIN32
	ssize_t len = recv(socket->descriptor, buf, count, 0);
#else
	ssize_t len = read(socket->descriptor, buf, count);
#endif

	return len;
}

ssize_t niosocket_write(struct niosocket *socket, const void *buf, size_t count)
{
	niodebug("writing socket: %d", socket->descriptor);

#ifdef WIN32
	ssize_t len = send(socket->descriptor, buf, count, 0);
#else
	ssize_t len = write(socket->descriptor, buf, count);
#endif

	return len;
}
